﻿using Bouyei.Geo.Converters;
using Bouyei.Geo.Geometries;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Bouyei.Geo.GeoParsers.EsriJson
{
    public class EsriJsonPoint : EsriJsonGeometry
    {
        public double x { get; set; }

        public double y { get; set; }
 
        public EsriJsonPoint() { }

        public EsriJsonPoint(string jsonString)
        {
            var geo = jsonConvert.ConvertTo<EsriJsonPoint>(jsonString);
            x = geo.x;
            y = geo.y;
            spatialReference = geo.spatialReference;
        }

        public EsriJsonPoint(Geometry geometry)
        {
            this.Geometry = geometry;
        }

        public Geometry ToGeometry()
        {
            this.Geometry= new Geometry(new Coordinate(x, y));
            this.Geometry.SRID = spatialReference.wkid;
            return this.Geometry;
        }

        public string FromGeometry()
        {
            var p = this.Geometry.GetGemoetry(0)[0][0];
            this.x = p.X;
            this.y = p.Y;
            this.spatialReference = new EsriJsonSpatialReference()
            {
                wkid = this.Geometry.SRID
            };

            return jsonConvert.ConvertFrom(this);
        }
    }

    public class EsriJsonLineString : EsriJsonGeometry
    {
        public List<List<double[]>> paths { get; set; }

        public EsriJsonLineString(string jsonString)
        {
            var geo = jsonConvert.ConvertTo<EsriJsonLineString>(jsonString);
            this.hasM = geo.hasM;
            this.hasZ = geo.hasZ;
            this.spatialReference = geo.spatialReference;
            this.paths = geo.paths;
        }

        public EsriJsonLineString(Geometry geometry)
        {
            this.Geometry = geometry;
        }

        public EsriJsonLineString() { }

        public Geometry ToGeometry()
        {
            GeoSequence[] seqs = new GeoSequence[paths.Count];
            for (int j = 0; j < paths.Count; ++j)
            {
                var line = paths[j];
                Coordinate[] coords = new Coordinate[line.Count];
                for (int i = 0; i < line.Count; ++i)
                {
                    coords[i] = new Coordinate()
                    {
                        X = line[i][0],
                        Y = line[i][1],
                        Z = hasZ ? line[i][2] : 0
                    };
                }
                GeoSequence seq = new GeoSequence(coords);
                seqs[j] = seq;
            }

            var gtype = seqs.Length > 1 ?
                GeoType.MULTILINESTRING : hasZ ?
                GeoType.LINESTRINGZ : GeoType.LINESTRING;

            var geo = new Geometry(gtype, seqs);
            geo.SRID = spatialReference.wkid;
            return geo;
        }

        public string FromGeometry()
        {
            this.spatialReference = new EsriJsonSpatialReference()
            {
                wkid = Geometry.SRID
            };
            var geo = Geometry.GetGemoetry(0);
            paths = new List<List<double[]>>(2);

            for(int i = 0; i < geo.Count; ++i)
            {
                var seq = geo[i];

                List<double[]> coords = new List<double[]>(seq.Count);
                for(int j = 0; j < seq.Count; ++j)
                {
                    var coord = seq[j];
                    coords.Add(new double[] { coord.X, coord.Y });
                }
                paths.Add(coords);
            }

            return jsonConvert.ConvertFrom(this);
        }
    }

    public class EsriJsonPolygon : EsriJsonGeometry
    {
        public List<List<double[]>> rings { get; set; }

        public EsriJsonPolygon(string jsonString)
        {
            var geo = new JsonExtensions().ConvertTo<EsriJsonPolygon>(jsonString);
            this.hasM = geo.hasM;
            this.hasZ = geo.hasZ;
            this.spatialReference = geo.spatialReference;
            this.rings = geo.rings;
        }

        public EsriJsonPolygon(Geometry geometry)
        {
            this.Geometry = geometry;
        }

        public EsriJsonPolygon() { }

        public Geometry ToGeometry()
        {
            List<List<GeoSequence>> geoSequences = new List<List<GeoSequence>>(2);

            List<GeoSequence> holes = new List<GeoSequence>(1);
            List<GeoSequence> shellRings = new List<GeoSequence>(2);

            for (int j = 0; j < rings.Count; ++j)
            {
                var line = rings[j];
                Coordinate[] coords = new Coordinate[line.Count];
                for (int i = 0; i < line.Count; ++i)
                {
                    coords[i] = new Coordinate()
                    {
                        X = line[i][0],
                        Y = line[i][1],
                        Z = hasZ ? line[i][2] : 0
                    };
                }
                GeoSequence seq = new GeoSequence(coords);
                var orientation = seq.GetOrientation();
                if (orientation == GeoOrientation.Clockwise)
                {
                    if (shellRings.Count > 0)
                    {
                        shellRings.AddRange(holes);

                        geoSequences.Add(new List<GeoSequence>(shellRings));
                        holes.Clear();
                        shellRings.Clear();
                    }
                    shellRings.Add(seq);
                }
                else
                {
                    holes.Add(seq);
                }
            }

            geoSequences.Add(new List<GeoSequence>(shellRings));

            GeoType gtype = geoSequences.Count > 1 ?
                GeoType.MULTIPOLYGON : hasZ ?
                GeoType.POLYGON : GeoType.POLYGONZ;

            var geo = new Geometry(gtype, geoSequences);

            geo.SRID = spatialReference.wkid;

            return geo;
        }

        public string FromGeometry()
        {
            this.spatialReference = new EsriJsonSpatialReference()
            {
                wkid = Geometry.SRID
            };
            int geometryCnt = Geometry.GeometryCount;
            rings = new List<List<double[]>>(geometryCnt << 1);

            for (int k = 0; k < geometryCnt; ++k)
            {
                var geo = Geometry.GetGemoetry(k);

                for (int i = 0; i < geo.Count; ++i)
                {
                    var seq = geo[i];

                    List<double[]> coords = new List<double[]>(seq.Count);
                    for (int j = 0; j < seq.Count; ++j)
                    {
                        var coord = seq[j];
                        coords.Add(new double[] { coord.X, coord.Y });
                    }
                    rings.Add(coords);
                }
            }
            return jsonConvert.ConvertFrom(this);
        }
    }

    public class EsriJsonSpatialReference
    {
        public int wkid { get; set; }
    }

    public class EsriJsonGeometry
    {
        public bool hasZ { get; set; } = false;
        public bool hasM { get; set; } = false;
        public EsriJsonSpatialReference spatialReference { get; set; }

        [System.Text.Json.Serialization.JsonIgnore]
        public Geometry Geometry { get; protected set; }

        protected JsonExtensions jsonConvert;

        public EsriJsonGeometry()
        {
            jsonConvert = new JsonExtensions();
        }
    }
}
